package com.chenjia.proto.converter;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.protobuf.Message;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

/**
 * 对之前基于反射版本的优化实现，优化点在于使用 <a href="https://github.com/EsotericSoftware/reflectasm">EsotericSoftware/reflectasm</a>
 * 代替反射调用方法。
 *
 * @see ReflectConverter
 */
final class ReflectAsmConverter implements MessageConverter {
    private ReflectAsmConverter() {
    }

    private static volatile ReflectAsmConverter instance;

    public static ReflectAsmConverter converter() {
        if (instance != null) return instance;
        return getInstance();
    }

    private static synchronized ReflectAsmConverter getInstance() {
        if (instance == null) {
            instance = new ReflectAsmConverter();
        }
        return instance;
    }

    /**
     * 将 {@link Message} 对象转为 pojo
     */
    @Override
    public <P, M extends Message> P toPojo(final Class<P> pojoClass, final M message) {
        if (message == null) {
            return null;
        }
        P pojo = createPojo(pojoClass);
        final Class<? extends Message> messageClass = message.getClass();

        final Pair<? extends Message, ?> pair = new Pair<>(messageClass, pojoClass);
        final Map<Integer, Integer> toPojoModel = ReflectAsmManager.getToPojoModel(pair);
        final MethodAccess messageAccess = ReflectAsmManager.getMethodAccess(messageClass);
        final MethodAccess pojoAccess = ReflectAsmManager.getMethodAccess(pojoClass);
        toPojoModel.forEach((getIndex, setIndex) -> {
            final Object invoke = messageAccess.invoke(message, getIndex);
            pojoAccess.invoke(pojo, setIndex, invoke);
        });
        return pojo;
    }

    private <T> T createPojo(final Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Default constructor not found for " + clazz.getSimpleName(), e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Make default constructor of " + clazz.getSimpleName() + " public", e);
        }
    }

    /**
     * 将 pojo 对象转为 {@link Message}
     */
    @SuppressWarnings("unchecked")
    @Override
    public <P, M extends Message> M toMessage(final Class<M> messageClass, final P pojo) {
        if (pojo == null) {
            return null;
        }
        M.Builder builder = createMessageBuilder(messageClass);
        final Class<? extends Message.Builder> builderClass = builder.getClass();
        final Class<?> pojoClass = pojo.getClass();
        Pair<?, ? extends Message.Builder> pair = new Pair<>(pojoClass, builderClass);
        final Map<Integer, Integer> toMessageModel = ReflectAsmManager.getToMessageModel(pair);
        final MethodAccess buildAccess = ReflectAsmManager.getMethodAccess(builderClass);
        final MethodAccess pojoAccess = ReflectAsmManager.getMethodAccess(pojoClass);
        toMessageModel.forEach((getIndex, setIndex) -> {
            final Object invoke = pojoAccess.invoke(pojo, getIndex);
            buildAccess.invoke(builder, setIndex, invoke);
        });
        return (M) builder.build();
    }

    private <M extends Message> M.Builder createMessageBuilder(final Class<M> messageClass) {
        try {
            return (M.Builder) messageClass.getDeclaredMethod("newBuilder").invoke(null);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Can't access 'newBuilder()' method for " + messageClass.getName(), e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Can't instantiate protobuf builder for " + messageClass.getName(), e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Method 'newBuilder()' not found in " + messageClass.getName(), e);
        }
    }

}
